﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DotNetImage.Imaging
{

    /// <summary>
    /// Universal structure for any type of images.
    /// In case of the grayscale one, value will be stored in Red channel
    /// </summary>
    public struct Pixel
    {
        public uint MaxValue;
        public int ColorChannelCount;

        public uint Red;
        public uint Green;
        public uint Blue;
        public uint Alpha;

        public uint GetChannel(ColorChannelIndex channel)
        {
            switch (channel)
            {
                case ColorChannelIndex.Red:
                    return Red;
                case ColorChannelIndex.Green:
                    return Green;
                case ColorChannelIndex.Blue:
                    return Blue;
                case ColorChannelIndex.Alpha:
                    return Alpha;
                default:
                    throw new NotSupportedException();
            }
        }

        public void SetChannel(ColorChannelIndex channel, uint value)
        {
            switch (channel)
            {
                case ColorChannelIndex.Red:
                    Red = value;
                    break;
                case ColorChannelIndex.Green:
                    Green = value;
                    break;
                case ColorChannelIndex.Blue:
                    Blue = value;
                    break;
                case ColorChannelIndex.Alpha:
                    Alpha = value;
                    break;
                //case ColorChannelIndex.AllChannels:
                //    Red = Green = Blue = Alpha = value;
                //    break;
                default:
                    throw new NotSupportedException();
            }
        }

        public void SetRGB(uint r, uint g, uint b)
        {
            Red = r;
            Green = g;
            Blue = b;
        }

        public void SetRGBA(uint r, uint g, uint b, uint a)
        {
            Red = r;
            Green = g;
            Blue = b;
            Alpha = a;
        }

        public void AddRGB(long r, long g, long b)
        {
            ProcessColor(ref this.Red, ref r, this.MaxValue);
            if (this.ColorChannelCount >= 3)
            {
                ProcessColor(ref this.Green, ref g, this.MaxValue);
                ProcessColor(ref this.Blue, ref b, this.MaxValue);
            }
        }

        private static void ProcessColor(ref uint color, ref long shift, uint MaxValue)
        {
            if (shift == 0)
            {
                return;
            }
            if (shift > 0)
            {
                color = (shift + color > MaxValue) ? MaxValue : (uint)(shift + color);
                return;
            }
            shift = Math.Abs(shift);
            color = (uint)(color > shift ? color - shift : 0);
            return;
        }

        public static Pixel GetMax(uint threshold, params Pixel[] pixels)
        {
            Pixel result = new Pixel();
            foreach (Pixel pix in pixels)
            {
                if (result.Red < pix.Red) result.Red = pix.Red;
                if (pix.ColorChannelCount >= 3)
                {
                    if (result.Green < pix.Green) result.Green = pix.Green;
                    if (result.Blue < pix.Blue) result.Blue = pix.Blue;
                    if (pix.ColorChannelCount == 4)
                    {
                        if (result.Alpha < pix.Alpha) result.Alpha = pix.Alpha;
                    }
                }
            }
            return result;
        }


        #region Arythmetic operations
        public void Add(Pixel pix)
        {
            Red = (uint)CheckUpperBorder((long)Red + (long)pix.Red);
            if (ColorChannelCount >= 3)
            {
                Green = (uint)CheckUpperBorder((long)Green + (long)pix.Green);
                Blue = (uint)CheckUpperBorder((long)Blue + (long)pix.Blue);
                if (ColorChannelCount == 4)
                {
                    Alpha = (uint)CheckUpperBorder((long)Alpha + (long)pix.Alpha);
                }
            }
        }

        public void Subtract(Pixel pix)
        {
            Red = (Red > pix.Red ? Red - pix.Red : 0);
            if (ColorChannelCount >= 3)
            {
                Green = (Green > pix.Green ? Green - pix.Green : 0);
                Blue = (Blue > pix.Blue ? Blue - pix.Blue : 0);
                if (ColorChannelCount == 4)
                {
                    Alpha = (Alpha > pix.Alpha ? Alpha - pix.Alpha : 0);
                }
            }
        }

        public void SubtractAbs(Pixel pix)
        {
            Red = (Red > pix.Red ? Red - pix.Red : pix.Red - Red);
            if (ColorChannelCount >= 3)
            {
                Green = (Green > pix.Green ? Green - pix.Green : pix.Green - Green);
                Blue = (Blue > pix.Blue ? Blue - pix.Blue : pix.Blue - Blue);
                if (ColorChannelCount == 4)
                {
                    Alpha = (Alpha > pix.Alpha ? Alpha - pix.Alpha : pix.Alpha - Alpha);
                }
            }
        }

        /// <summary>
        /// Multiplies all channels values. If parameter value == 1, this channel does not get multiplied.
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <param name="a"></param>
        public void Multiply(double r, double g, double b, double a)
        {
            Red = Multiply(Red, r);
            if (ColorChannelCount >= 3)
            {
                Green = Multiply(Green, g);
                Blue = Multiply(Blue, b);
                if (ColorChannelCount == 4)
                {
                    Alpha = Multiply(Alpha, a);
                }
            }
        }

        private uint Multiply(uint color, double factor)
        {
            if (factor > 1)
            {
                return (uint)CheckUpperBorder((long)(color * factor));
            }
            else if (factor < 0)
            {
                return 0;
            }
            else
            {
                return (uint)(color * factor);
            }
        }

        /// <summary>
        /// Divide all channels on given double values
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <param name="a"></param>
        public void Divide(double r, double g, double b, double a)
        {
            Red = Divide(Red, r);
            if (ColorChannelCount >= 3)
            {
                Green = Divide(Green, g);
                Blue = Divide(Blue, b);
                if (ColorChannelCount == 4)
                {
                    Alpha = Divide(Alpha, a);
                }
            }
        }

        private uint Divide(uint color, double factor)
        {
            if (factor > 0)
            {
                if (factor < 1)
                {
                    return (uint)CheckUpperBorder((long)(color / factor));
                }
                else
                {
                    return (uint)(color / factor);
                }

            }
            else if (factor < 0)
            {
                return 0;
            }
            else
            {
                // Divide on zero - return original value
                return color;
            }
        }
        #endregion


/*
        public static Pixel operator -(Pixel left, Pixel right)
        {
            Pixel result = left.Clone();
            result.Red = result.Red > right.Red ? result.Red - right.Red : 0;
            if (result.ColorChannelCount >= 3)
            {
                result.Green = result.Green > right.Green ? result.Green - right.Green : 0;
                result.Blue = result.Blue > right.Blue ? result.Blue - right.Blue : 0;
                if (result.ColorChannelCount == 3)
                {
                    result.Alpha = result.Alpha > right.Alpha ? result.Alpha - right.Alpha : 0;
                }
            }
            return result;
        }
*/

        public static Pixel operator +(Pixel left, Pixel right)
        {
            Pixel result = left.Clone();
            result.Red = left.Red > right.Red ? left.Red - right.Red : 0;
            if (result.ColorChannelCount >= 3)
            {
                result.Green = result.Green > right.Green ? result.Green - right.Green : 0;
                result.Blue = result.Blue > right.Blue ? result.Blue - right.Blue : 0;
                if (result.ColorChannelCount == 3)
                {
                    result.Alpha = result.Alpha > right.Alpha ? result.Alpha - right.Alpha : 0;
                }
            }
            return result;
        }



        public Pixel Clone()
        {
            Pixel result = new Pixel();
            result.ColorChannelCount = this.ColorChannelCount;
            result.MaxValue = this.MaxValue;
            result.Red = this.Red;
            result.Green = this.Green;
            result.Blue = this.Blue;
            return result;
        }


        private long CheckUpperBorder(long value)
        {
            return value <= MaxValue ? value : MaxValue;
        }

        private long CheckLowerBorder(long value)
        {
            return value <= 0 ? 0 : value;
        }
    }
}
